﻿using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Collections;
using System.IO;
using System.ComponentModel;
using System.Text;

public partial class Util
{
    // Use a circular buffer for read (read half at a time)
    public static readonly long BUFFER_SIZE = 0x1000;
    public static readonly long BUFFER_SIZE_D2 = (BUFFER_SIZE >> 1);
    
    class RowInBuffer
    {
        public byte[] buffer = new byte[BUFFER_SIZE];
        public byte bRowDelimiter;
        public long lSlicePosRecStart = 0;
        public long lSlicePosRecEnd = 1;
        public long lSlicePos = 0;
        public long lSliceLength;
        public FileStream f;
        public RowInBuffer(string sFileName, byte bRowDelimiterValue, int iSlice, int iSliceCount)
        {
            bRowDelimiter = bRowDelimiterValue;

            f = File.OpenRead(sFileName);
            long lFileSize = f.Length;

            // Get absolute positions of slice
            long lSliceStart = GetSliceStartValue(lFileSize, iSlice, iSliceCount);
            long lSliceEnded = GetSliceStartValue(lFileSize, iSlice + 1, iSliceCount);
            lSliceLength = lSliceEnded - lSliceStart;

            // Jump to your slice
            f.Seek(lSliceStart, SeekOrigin.Begin);
        }
        private static long GetSliceStartValue(long lFileSize, int iSlice, int iSliceCount)
        {
            if (iSlice > iSliceCount)
                throw new Exception("Slice out of range");
            if (iSlice == iSliceCount)
                return lFileSize;
            return (iSlice * lFileSize) / iSliceCount;
        }
        private byte GetByte(FileStream f)
        {
            long lBufferPos = lSlicePos % BUFFER_SIZE;
            if (lBufferPos == 0)
            { // Read 1st half of buffer (again?)
                f.Read(buffer, 0, (int)BUFFER_SIZE_D2);
            }
            else if (lBufferPos == BUFFER_SIZE_D2)
            { // Read 2nd half of buffer (again?)
                f.Read(buffer, (int)BUFFER_SIZE_D2, (int)BUFFER_SIZE_D2);
            }
            return buffer[lBufferPos];
        }

        public bool GetRowEnd()
        {
            GetByte(f);
            lSlicePosRecEnd = lSlicePosRecStart + 2;
            return true;
        }
        public bool SkipRow()
        {
            GetByte(f);
            lSlicePosRecEnd = lSlicePosRecStart + 2;
            return true;
        }

        //static Encoding encoding = new ASCIIEncoding();
        public string GetCurrentRow()
        {
            string s;
            s = "";
            if (lSlicePosRecStart < lSlicePosRecEnd)
            { // Normal span
                for (long i = lSlicePosRecStart; i < lSlicePosRecEnd; i++)
                    s += (char)buffer[i];
            }
            else if (lSlicePosRecStart > lSlicePosRecEnd && lSlicePosRecStart > BUFFER_SIZE_D2 && lSlicePosRecEnd < BUFFER_SIZE_D2)
            { // Wrapping around the buffer
                for (long i = lSlicePosRecStart; i < lSlicePosRecEnd; i++)
                    s += (char)buffer[i];
            }
            else
            { // Buffer too large or invalid, return 200 blank fields
                s = ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;";
            }
            //s = new string((char[])(buffer), (int)lSlicePosRecStart, (int)(lSlicePosRecEnd - lSlicePosRecStart));
            /*
            fixed (byte* pb = (buffer))
            {
                sbyte* psb = (sbyte*)pb;
                s = new string(psb, (int)lSlicePosRecStart, (int)(lSlicePosRecEnd - lSlicePosRecStart), encoding);
            }//*/
            return s;
        }
    }

    [Microsoft.SqlServer.Server.SqlFunction(
        FillRowMethodName="SlicedFileFill",TableDefinition="row NVARCHAR(4000)")]
    public static IEnumerable SlicedFile(string sFileName, byte bRowDelimiter, int iSlice, int iSliceCount)
    {
        RowInBuffer r = new RowInBuffer(sFileName, bRowDelimiter, iSlice, iSliceCount);

        // Get end of "row"
        if (r.GetRowEnd())
        {
            // Read and return until slice done
            while (r.SkipRow() && r.GetRowEnd())
            {
                yield return r;
            }
        }
    }
    
    public static void SlicedFileFill(object o, out string s)
    {
        s = ((RowInBuffer)o).GetCurrentRow();
    }
};